package com.mallcloud.mall.app.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.mallcloud.mall.app.api.constant.ApiConstants;
import com.mallcloud.mall.app.api.entity.dto.AppXglQRCodePaymentInfoDTO;
import com.mallcloud.mall.app.api.entity.dto.AppXglQRCodePaymentLoginDTO;
import com.mallcloud.mall.app.api.entity.vo.*;
import com.mallcloud.mall.app.config.XglQRCodePaymentConfig;
import com.mallcloud.mall.app.mapper.AppProcedureMapper;
import com.mallcloud.mall.app.mapper.AppXglQRCodePaymentMapper;
import com.mallcloud.mall.app.service.AppOrderService;
import com.mallcloud.mall.app.service.AppXglQRCodePaymentService;
import com.mallcloud.mall.common.core.constant.enums.RLockEnum;
import com.mallcloud.mall.common.core.constant.enums.mall.OrderEnum;
import com.mallcloud.mall.common.core.exception.FailException;
import com.mallcloud.mall.common.core.util.HttpUtil;
import com.mallcloud.mall.common.data.resolver.TenantResolver;
import com.mallcloud.mall.mall.api.entity.MallOrderModel;
import com.mallcloud.mall.mall.api.entity.dto.ProXglQRCodePaymentAccumulatePoints;
import com.mallcloud.mall.mall.api.entity.dto.ProXglQRCodePaymentCouponWriteOff;
import com.mallcloud.mall.mall.service.MallOrderService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty("const.pay.url")
public class AppXglQRCodePaymentServiceImpl implements AppXglQRCodePaymentService {

	private final HMac xglPayHmac;
	private final HttpUtil httpUtil;
	private final Redisson redisson;
	private final MallOrderService orderService;
	private final AppOrderService appOrderService;
	private final AppProcedureMapper appProcedureMapper;
	private final XglQRCodePaymentConfig xglPayConfig;
	private final AppXglQRCodePaymentMapper appXglQRCodePaymentMapper;

	@Override
	public XglQRCodePaymentInfoVO getPayInfo(AppXglQRCodePaymentInfoDTO dto) {
		String orderNo = String.valueOf(IdUtil.getSnowflake().nextId());
		String timestamp = String.valueOf(System.currentTimeMillis());
		Map<String, Object> requestMap = MapUtil.builder(new HashMap<String, Object>())
				.put("amount", String.valueOf(dto.getAmount()))
				.put("mch_id", dto.getMchId())
				.put("mch_notify_url", xglPayConfig.getNotifyUrl())
				.put("mch_order_no", orderNo)
				.put("timestamp", timestamp).build();
		requestMap.put("sign", sign(requestMap));//签名

		log.info("请求参数: {}", JSONObject.toJSONString(requestMap));
		XglQRCodePaymentInfoVO responseVO = httpUtil.exchange(
				StrUtil.format("{}{}", xglPayConfig.getUrl(), ApiConstants.XglApi.QRCODE_PAYMENT_INFO.getUrl()),
				ApiConstants.XglApi.QRCODE_PAYMENT_INFO.getMethod(),
				null,
				null,
				JSONObject.toJSONString(requestMap),
				ApiConstants.XglApi.QRCODE_PAYMENT_INFO.getResponseClass()
		);
		log.info("请求返回: {}", JSONObject.toJSONString(responseVO));
		if (!responseVO.isSuccess()) {
			throw new FailException(responseVO.getErrMsg());
		}
		return responseVO;
	}

	@Override
	public Boolean merchantLogin(AppXglQRCodePaymentLoginDTO dto) {
		dto.setPasswordEncrypt(getPasswordEncrypt(dto.getPassword()));
		Long count = appXglQRCodePaymentMapper.merchantLogin(dto);
		if (count == 0) {
			throw new FailException("用户不存在");
		}
		return Boolean.TRUE;
	}

	@Override
	public Map<String, Object> notify(HttpServletRequest request) {
		RLock lock = null;
		boolean tryLock = false;
		XglQRCodePaymentNotifyVO vo = null;
		try {
			vo = getXglQRCodePaymentNotifyVO(request);
			String mchOrderNo = vo.getMchOrderNo();
			String timestamp = vo.getTimestamp();

			log.info("星光里扫码支付回调-请求锁");
			lock = redisson.getLock(RLockEnum.XGL_QRCODE_PAYMENT_NOTIFY.getKey(mchOrderNo));
			//尝试加锁，最多等待5秒，上锁以后10秒自动解锁
			tryLock = lock.tryLock(5, 10, TimeUnit.SECONDS);
			if (!tryLock) {
				log.error("未获得锁，系统繁忙");
				throw new Exception("系统繁忙");
			}

			MallOrderModel order = orderService.getOne(new LambdaQueryWrapper<MallOrderModel>()
					.eq(MallOrderModel::getOrderNo, mchOrderNo)
			);
			if (Objects.isNull(order)) {
				log.error("[{}]订单数据丢失，需要手动补销售数据", mchOrderNo);
				throw new Exception("订单数据丢失");
			}
			if (order.getHasPayed()) {
				log.error("[{}]订单重复回调拦截", order.getOrderNo());
				return success();
			}
			DateTime successTime = DateUtil.parse(timestamp, DatePattern.PURE_DATETIME_PATTERN);
			orderService.update(new LambdaUpdateWrapper<MallOrderModel>()
					.eq(MallOrderModel::getOrderNo, mchOrderNo)
					.set(MallOrderModel::getHasPayed, Boolean.TRUE)
					.set(MallOrderModel::getPayTime, successTime)
			);
			order.setHasPayed(true);
			order.setPayTime(successTime);

			//处理订单
			this.handleOrder(order);
			return success();
		}
		catch (Exception e) {
			if (Objects.nonNull(vo)) {
				appOrderService.updateTryFail(vo.getMchOrderNo(), OrderEnum.StatusEnum.FAIL.getStatus(), e.getMessage());
			}
			return fail(e.getMessage());
		}
		finally {
			lock.unlock();
		}
	}

	/**
	 * 解析回调请求的数据
	 *
	 * @param request
	 * @return
	 */
	@SneakyThrows
	private XglQRCodePaymentNotifyVO getXglQRCodePaymentNotifyVO(HttpServletRequest request) {
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
			StringBuilder stringBuilder = new StringBuilder();
			String line;
			while ((line = reader.readLine()) != null) {
				stringBuilder.append(line);
			}
			String body = stringBuilder.toString();
			log.info("异步通知成功: {}", body);
			//验签
			XglQRCodePaymentNotifyVO vo = JSONObject.toJavaObject(JSONObject.parseObject(body), XglQRCodePaymentNotifyVO.class);
			String mchOrderNo = vo.getMchOrderNo();
			if (!checkSign(body)) {
				appOrderService.updateTryFail(mchOrderNo, 9, "验签失败");
				log.error("验签失败");
				throw new Exception("验签失败");
			}
			return vo;
		}
		catch (IOException e) {
			log.error(e.getMessage(), e);
			throw new Exception("回调请求参数读取失败", e);
		}
	}

	/**
	 * 处理回调请求
	 *
	 * @param order
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public void handleOrder(MallOrderModel order) {
		log.info("[{}]订单数据：{}", order.getOrderNo(), JSON.toJSONString(order));
		if (order.getStatus() == OrderEnum.StatusEnum.SUCCESS.getStatus()) {
			log.error("[{}]订单重复回调拦截", order.getOrderNo());
			return;
		}
		//销售数据上传
		//1.上传交易小票头 返回交易序号billno
		ProXglSendSaleHead sendSaleHead = new ProXglSendSaleHead();
		sendSaleHead.setOrderNo(order.getOrderNo());
		sendSaleHead.setStoreNo(order.getStoreNo());
		sendSaleHead.setPosKey(order.getPosKey());
		sendSaleHead.setSaleTime(DateUtil.format(order.getPayTime(), DatePattern.NORM_DATETIME_PATTERN));
		sendSaleHead.setPhone(Optional.ofNullable(order).map(MallOrderModel::getPhone).orElse(null));
		sendSaleHead.setTotalMoney(Optional.ofNullable(order.getMoney()).map(x -> x.toString()).orElse(null));
		sendSaleHead.setFinalPayMoney(Optional.ofNullable(order.getFinalMoney()).map(x -> x.toString()).orElse(null));
		sendSaleHead.setChangeAmount("0");
		String billNo = this.xglSendSaleHead(sendSaleHead);

		//2.上传交易商品明细
		ProXglSendSaleGoods sendSaleGoods = new ProXglSendSaleGoods();
		sendSaleGoods.setOrderNo(order.getOrderNo());
		sendSaleGoods.setStoreNo(order.getStoreNo());
		sendSaleGoods.setPosKey(order.getPosKey());
		sendSaleGoods.setBillNo(billNo);
		this.xglSendSaleGoods(sendSaleGoods);

		//3.上传支付明细
		ProXglSendSalePay sendSalePay = new ProXglSendSalePay();
		sendSalePay.setBillNo(billNo);
		sendSalePay.setPayMoney(Optional.ofNullable(order.getFinalMoney()).map(x -> x.toString()).orElse(null));
		sendSalePay.setPayNo(Optional.ofNullable(order.getOrderNo()).orElse(null));//订单号
		this.xglSendSalePay(sendSalePay);

		String couponNo = order.getCouponNo();
		if (StrUtil.isNotBlank(couponNo)) {
			// 4.(使用订单券抵扣时）上传支付明细
			ProXglSendSalePay couponSendSalePay = new ProXglSendSalePay();
			couponSendSalePay.setBillNo(billNo);
			//如果券没溢余就传券金额，如果溢余了就传应付金额
			if (order.getMoney().compareTo(order.getCouponMoney()) >= 0) {
				couponSendSalePay.setPayMoney(order.getCouponMoney().toString());
			}
			else {
				couponSendSalePay.setPayMoney(order.getMoney().toString());
			}
			couponSendSalePay.setPayNo(Optional.ofNullable(order.getCouponNo()).orElse(null));//用了券就用券号
			couponSendSalePay.setPayCode(order.getDiscountType() == 1 ? "0708" : "0709");
			this.xglSendSalePay(couponSendSalePay);

			// 5.核销券
			ProXglQRCodePaymentCouponWriteOff paymentCouponWriteOff = new ProXglQRCodePaymentCouponWriteOff();
			paymentCouponWriteOff.setCouponId(order.getCouponNo());
			paymentCouponWriteOff.setWriteOffMoney(String.valueOf(order.getCouponMoney()));
			paymentCouponWriteOff.setWriteOffStoreNo(order.getStoreNo());
			paymentCouponWriteOff.setPayMethod(order.getDiscountType() == 1 ? "0708" : "0709");
			paymentCouponWriteOff.setPosKey(order.getPosKey());
			this.writeOff(paymentCouponWriteOff);

			// 6.累积积分
			ProXglQRCodePaymentAccumulatePoints xglQRCodePaymentAccumulatePoints = new ProXglQRCodePaymentAccumulatePoints();
			xglQRCodePaymentAccumulatePoints.setStoreNo(order.getStoreNo());
			xglQRCodePaymentAccumulatePoints.setField1("002");
			xglQRCodePaymentAccumulatePoints.setBillNo(billNo);
			this.accumulatePoints(xglQRCodePaymentAccumulatePoints);
		}

		orderService.update(new LambdaUpdateWrapper<MallOrderModel>()
				.eq(MallOrderModel::getId, order.getId())
				.set(MallOrderModel::getStatus, OrderEnum.StatusEnum.SUCCESS.getStatus())
		);
	}

	/**
	 * 上传销售数据
	 *
	 * @return billNo 交易序号
	 */
	@Override
	public String xglSendSaleHead(ProXglSendSaleHead sendSaleHead) {
		appProcedureMapper.xglSendSaleHead(sendSaleHead);
		if (!sendSaleHead.isSuccess()) {
			log.error("上传交易小票头失败");
			throw new FailException(sendSaleHead.getMessage());
		}
		return sendSaleHead.getBillNo();
	}

	/**
	 * 上传交易商品明细
	 */
	@Override
	public void xglSendSaleGoods(ProXglSendSaleGoods dto) {
		appProcedureMapper.xglSendSaleGoods(dto);
		if (!dto.isSuccess()) {
			log.error("上传交易商品明细失败");
			throw new FailException(dto.getMessage());
		}
	}

	/**
	 * 上传支付明细
	 */
	@Override
	public void xglSendSalePay(ProXglSendSalePay dto) {
		appProcedureMapper.xglSendSalePay(dto);
		if (!dto.isSuccess()) {
			log.error("上传支付明细失败");
			throw new FailException(dto.getMessage());
		}
	}

	@Override
	public void writeOff(ProXglQRCodePaymentCouponWriteOff dto) {
		appProcedureMapper.xglQRCodePaymentCouponWriteOff(dto);
		if (!dto.isSuccess()) {
			log.error("手工券核销失败");
			throw new FailException(dto.getMessage());
		}
	}

	@Override
	public void accumulatePoints(ProXglQRCodePaymentAccumulatePoints dto) {
		if(StrUtil.isBlank(dto.getStoreNo())){
			dto.setStoreNo(TenantResolver.getById().getCode());
		}
		appProcedureMapper.xglQRCodePaymentAccumulatePoints(dto);
		if (!dto.isSuccess()) {
			log.error("失败");
			throw new FailException(dto.getMessage());
		}
	}

	/**
	 * 密码密文
	 *
	 * @param pwd
	 * @return
	 */
	@SneakyThrows
	private String getPasswordEncrypt(String pwd) {
		if (StrUtil.isBlank(pwd)) {
			return "";
		}
		int len = pwd.length();
		int k;
		boolean signal = false; // 表示后面的字节是否需要根据前面的字节组成一个汉字
		byte[] b = new byte[pwd.length()];
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < len; i++) {
			k = 255 - pwd.charAt(i);
			for (int j = 0; j < (len - i); j++) {
				k = ((2 * k) % 256) + (k / 128);
			}
			sb.append(String.format(",%s", k));
			b[i] = (byte) k;
			if (signal) {
				signal = false;
			}
			else {
				if (k > 127) {
					signal = true;
				}
			}
		}
		try {
			return sb.substring(1);
		}
		catch (Exception e) {
			throw new Exception("密码解析异常");
		}
	}

	/**
	 * 检验签名
	 *
	 * @param body
	 * @return
	 */
	private boolean checkSign(String body) {
		Map parse1 = JSONUtil.parseObj(body);
		Map o = ((Map<String, String>) parse1).entrySet().stream()
				.filter(v -> StrUtil.isNotBlank(v.getValue()))
				.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue()));
		Object sign = o.get("sign");
		o.remove("sign");
		return sign.equals(sign(o));
	}

	/**
	 * 签名
	 *
	 * @param map
	 * @return
	 */
	private String sign(Map<String, Object> map) {
		String sortJoin = MapUtil.sortJoin(map, "&", "=", true);
		return xglPayHmac.digestHex(sortJoin.concat(StrUtil.format("&key={}", xglPayConfig.getKey()))).toUpperCase();
	}

	//--回调返回体---------------------------------------------------------
	private Map<String, Object> success() {
		Map<String, Object> build = MapBuilder.create(new HashMap<String, Object>())
				.put("result_code", "SUCCESS")//SUCCESS 表示业务受理通讯成功，其它表示失败
				.put("err_code", "000000")//000000 表示交易成功，非 000000 表示交易失败，错误信息可以查看 err_msg 字段
				.put("err_msg", "支付成功")//业务失败，错误时返回
				.put("timestamp", DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN))//格式为yyyymmddhhmmss
				.build();
		build.put("sign", sign(build));
		return build;
	}

	private Map<String, Object> fail() {
		return fail("支付失败");
	}

	private Map<String, Object> fail(String msg) {
		Map<String, Object> build = MapBuilder.create(new HashMap<String, Object>())
				.put("result_code", "FAIL")//SUCCESS 表示业务受理通讯成功，其它表示失败
				.put("err_code", "000001")//000000 表示交易成功，非 000000 表示交易失败，错误信息可以查看 err_msg 字段
				.put("err_msg", msg)//业务失败，错误时返回
				.put("timestamp", DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN))//格式为yyyymmddhhmmss
				.build();
		build.put("sign", sign(build));
		return build;
	}
}
